package com.demo.core.dao.impl.base;

import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import com.demo.common.Utils;
import com.demo.core.cache.CacheKey;
import com.demo.core.cache.CacheWrapper;
import com.demo.core.dao.base.BaseCoreDao;
import com.demo.core.model.support.GeneralExecuteModel;
import com.demo.core.model.support.SimpleBaseModel;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryAware;
import org.springframework.beans.factory.BeanInitializationException;
import org.springframework.beans.factory.BeanNameAware;
import org.springframework.orm.ibatis.SqlMapClientCallback;
import org.springframework.orm.ibatis.support.SqlMapClientDaoSupport;
import org.springframework.util.Assert;

import com.ibatis.sqlmap.client.SqlMapExecutor;

import static org.springframework.util.ClassUtils.*;

/**
 * 基础数据访问层支持
 * <ol>
 * 	<li>提供CURD基础访问方法</li>
 * 	<li>提供通过主键(单主键)查询验证数据存在的方法</li>
 *  <li>提供数据缓存方法,实现DAO层的缓存</li>
 * 	<li>方法依赖GeneralExecuteSql.xml配置文件</li>
 * </ol>
 * @author ryuu.kk
 *
 * @param <T> 数据模型
 * @param <PK> 外键类型
 */
public abstract class BaseCoreDaoSupport<T extends SimpleBaseModel, PK extends java.io.Serializable> extends SqlMapClientDaoSupport implements BeanNameAware, BeanFactoryAware, BaseCoreDao<T, PK> {

	/**
	 * 日志
	 */
	protected static final Log LOG = LogFactory.getLog(BaseCoreDaoSupport.class);
	
	/**
	 * 基础执行Sql NameSpace
	 */
	protected static final String BASE_SPACE = "GeneralExecuteSpace";
	
	/**
	 * 数据存在对应操作
	 */
	protected static final String DATA_EXIST = "dataCount";
	
	/**
	 * 直接执行Sql语句
	 */
	protected static final String EXEC_SQL = "exeSql";
	
	/**
	 * modelName
	 * 当前模型名称,如果不指定模型名称 <br>
	 * 在执行对象映射则会自定根据参数对象的类型找到模型名称 <br>
	 * 例如 com.demo.model.User -> user <br>
	 * 这样做的目的是iBatis的update中参数分解为两类
	 * 一类是更新内容；一类是更新条件
	 * 但最终传入iBatis的SqlMap文件中都是以map形式传递
	 * 如何区分参数为哪一类型的,故采用模型的名称作为更新内容(一个模型对象)
	 */
	protected String modelName = "";
	
	/**
	 * 直接Sql执行模型
	 */
	protected GeneralExecuteModel<PK> model;
	
	/**
	 * 使用缓存标志
	 * 如果useCache = true则使用缓存处理对象
	 */
	private boolean useCache = false;

	/**
	 * 继承类在IoC容器中的BeanName
	 */
	private String beanName;
	
	/**
	 * 缓存名称
	 */
	private String cacheName;
	
	/**
	 * IoC容器
	 */
	private BeanFactory beanFactory;
	
	/**
	 * 缓存封装对象
	 */
	protected CacheWrapper<CacheKey, Object> cache;
	
	/**SQL表空间**/
	protected abstract String nameSpace();
	/**
	 * 初始化
	 */
	protected abstract void doInit(GeneralExecuteModel<PK> model);
	
	@Override
	public Long insert(T t) {
		
		if (t == null) {
			return null;
		}
		// 生成主键ID
		if (t.getId() == null) {
			// 不存在主键ID,则自动生成.
			t.setId(Utils.uuidShort());
			if (LOG.isInfoEnabled()) {
				LOG.info("not found [" + t + "]'s id, auto create id: " + t.getId());
			}
		}
		Long pk = t.getId();
		getSqlMapClientTemplate().insert(nameSpace() + "insert", t);
		/*//-- 保留由DB创建自增主键的原始代码
		Object generatedKey = getSqlMapClientTemplate().insert(nameSpace() + "insert", t);
		if (generatedKey == null) {
			LOG.debug(nameSpace() + " insert maybe procedure.");
			Method method = findMethod(t.getClass(), "getId");
			makeAccessible(method);
			try {
				generatedKey = method.invoke(t);
			} catch (IllegalAccessException e1) {
				LOG.error(e1.getMessage(), e1.getCause());
			} catch (IllegalArgumentException e1) {
				LOG.error(e1.getMessage(), e1.getCause());
			} catch (InvocationTargetException e1) {
				LOG.error(e1.getMessage(), e1.getCause());
			}
		}
		Long id = Long.parseLong(generatedKey.toString());
		*/
		// 清除缓存
		/* ***************************************************************
		 * 实际场景:已经query一个请求,这是新建一条记录,而query再次请求时,
		 * 不包含这条新建的条目,在粗粒度的操作为删除所有缓存.
		 * 另一种考虑是,只有R进行,CUD不进行缓存,在R缓存内容,10分钟失效?
		 * *************************************************************** */
		cacheClear();
		if (useCache && cache != null) {
			CacheKey key = new CacheKey();
			// key.update(t.getClass().getName().hashCode());
			Map<String, Object> params = new HashMap<String, Object>();
			params.put("id", pk);
			key.update(nameSpace());
			key.update("select");
			key.update(params.hashCode());
			cache.put(key, t);
		}
		
		return pk;
	}
	
	@Override
	public int update(T t, Map<String, Object> params) {
		Assert.notNull(t);
		Assert.notNull(params);
		params.put(getModelName(t), t);
		if (LOG.isDebugEnabled()) {
			LOG.debug("execute update method, mode is " + t.getClass().getSimpleName());
		}
		// 自动清除缓存,存在一个隐含问题,当select缓存存在一条数据,而更新时对该数据进行了编辑(更新条件与检索不同时),缓存不会及时更新。需要手动重置缓存。
		// 此问题由于检索和更新、删除无法统一条件所致。导致无法同步数据。
		// 由于无法解决该问题,则更新和删除操作,直接清除整个缓存空间。
//		if (useCache && cache != null) {
//			if (params != null && params.containsKey("id")) {
//				CacheKey key = new CacheKey();
//				key.update(params.hashCode());
//				cache.clear(key);
//			} else {
//				cache.clearAll();
//			}
//		}
		// 清除缓存
		cacheClear();
		
		return getSqlMapClientTemplate().update(nameSpace() + "update", params);
	}
	
	@Override
	public int delete(Map<String, Object> params) {
		//params.put(getModelName(t), t);
		if (LOG.isDebugEnabled()) {
			LOG.debug("execute delete method, params is " + params.values());
		}
		// 清除缓存
		cacheClear();
		int cnt = getSqlMapClientTemplate().delete(nameSpace() + "delete" , params);
		if (cnt == 0 && params.containsKey("cnt" )) {
			cnt = Integer.parseInt(params.get("cnt").toString());
		}
		return cnt;
	}
	
	@Override
	public int delete(PK id) {
		Map<String, Object> params = new HashMap<String, Object>();
		params.put("id", id);
		return delete(params);
	}
	
	@Override
	public boolean exists(PK id) {
		if (id == null) {
			return false;
		}
		//设置主键ID
		model.setId(id);
		return getSqlMapClientTemplate().execute(new SqlMapClientCallback<Boolean>() {
			public Boolean doInSqlMapClient(SqlMapExecutor executor) throws SQLException {
				Map<String, Object> params = new HashMap<String, Object>(2);
				params.put("model",  model);
				Integer cnt = (Integer) executor.queryForObject(BASE_SPACE + "." + DATA_EXIST, params);
				return cnt > 0;
			}
		});
	}
	
	@SuppressWarnings({ "unchecked", "rawtypes" })
	@Override
	public List<T> select(Map<String, Object> params) {
		List<T> result = null;
		CacheKey key  = null;
		if (useCache && cache != null) {
			key = new CacheKey();
			key.update(nameSpace());
			key.update("select");
			key.update(params.hashCode());
			Object obj = cache.get(key);
			
			if (LOG.isDebugEnabled()) {
				LOG.debug(" using cache :" + key.toString() + " result:" + (obj == null ? " is null" : obj.toString()));
			}
			if (obj instanceof List) {
				// 缓存对象为List
				result = (List<T>) obj;
			} else if (obj instanceof SimpleBaseModel) {
				// 缓存对象为单ID主键对象
				result = new ArrayList(Arrays.asList(obj));
			} else {
				// 其他类型,清除缓存
				cache.clear(key);
				result = null;
			}
		}
		if (result == null) {
			result = getSqlMapClientTemplate().queryForList(nameSpace() + "select", params);
			if (useCache && result != null && cache != null) {
				cache.put(key, result);
			}
		}
		
		return result;
	}

	@Override
	public T select(PK id) {
		Map<String, Object> params = new HashMap<String, Object>();
		params.put("id", id);
		List<T> tList = select(params);
		return tList.size() == 0 ? null : tList.get(0);
	}
	
	/**
	 * 返回模型名称
	 * 模型名称的首字母小写:com.demo.model.User -> user
	 * @param t 模型
	 * @return 模型名
	 */
	public String getModelName(T t) {
		if (modelName == null || "".equals(modelName)) {
			modelName = getShortNameAsProperty(t.getClass());
		}
		return modelName;
	}
	
	/**
	 * 初始化Dao
	 */
	@SuppressWarnings("unchecked")
	@Override
	public void initDao() {
		if ( model == null) {
			 model = new GeneralExecuteModel<PK>();
		}
		doInit(model);
		
		//验证SQL脚本空间是否定义
		if (nameSpace() == null || "".equals(nameSpace())) {
			throw new BeanInitializationException("BaseCoreDaoSupport not found sql-nameSpace ：'" + nameSpace() + "'; nameSpace must be not null.");
		}
		
		// 取得缓存管理器
		if (useCache && beanFactory != null) {
			// 根据Bean名取得Bean对应的Cache对象,如果不存在,则使用默认Cache名
			if (cacheName == null || cacheName.length() == 0) {
				cacheName = beanName + "Cache"; //CacheWrapper
			}
			try {
				this.cache = beanFactory.getBean(cacheName, com.demo.core.cache.ehcache.EhCacheWrapper.class);
			} catch (BeansException e) {
				if (LOG.isDebugEnabled()) {
					LOG.debug("CacheManager not found cache '" + cacheName + "'. use default 'coreDefaultCacheWrapper' cache");
				}
				this.cache = beanFactory.getBean("coreDefaultCache", com.demo.core.cache.ehcache.EhCacheWrapper.class);
			}
		}
	}
	
	public CacheWrapper<CacheKey, Object> getCache() {
		return cache;
	}
	
	public boolean isUseCache() {
		return useCache;
	}
	
	public void setUseCache(boolean useCache) {
		this.useCache = useCache;
		// 清除缓存,目的是可用动态调整是否使用缓存
		cacheClear();
	}
	
	/**
	 * 缓存名称,名称可由IoC容器注入;
	 * 如果不指定,则按照默认规则创建:beanName + "CacheWrapper"
	 * @param cacheName 缓存名
	 */
	public void setCacheName(String cacheName) {
		this.cacheName = cacheName;
	}
	
	@Override
	public void setBeanName(String beanName) {
		this.beanName = beanName;
	}
	
	@Override
	public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
		this.beanFactory = beanFactory;
	}
	
	protected void cacheClear() {
		// 自动清除缓存
		if (useCache && cache != null) {
			cache.clearAll();
		}
	}
}
